//
// Created by LiuYou on 2021/10/29.
//

#include <iostream>
#include <unistd.h>
#include <cstring>


static const int LEFT = 30000000;
static const int RIGHT = 30000200;
static const int THREAD_NUM = 4;


/*
 * 需要一个全局变量, 来进行线程间的通信。
 * 要做到线程间的通信, 比做到进程间通信要简单。
 */
static int number;

/*
 * 互斥量的销毁在 main线程 即将退出的时候销毁。
 * 使用这个互斥量是为了解决 在 main线程 和其它线程们
 * 访问 number 这个对象时造成冲突竞争。
 */
static pthread_mutex_t MUTEX = PTHREAD_MUTEX_INITIALIZER;


int main(int argc, char** argv) {
    /* ***************************************************** */
    /*
     * 要非常深刻的知道 main线程 以及创建出来的另外 4 个线程
     * 需要各自完成什么样子的任务。这需要非常的清楚与明确。
     * main thread:
     *      1. 创建 THREAD_NUM 个新线程, 即 4 个。
     *      2. 下发任务。
     * 其它 thread:
     *      1. 抢这个任务
     *      2. 如果抢到了任务那么就立刻执行它。
     */
    /* ***************************************************** */

    pthread_t tidArray[THREAD_NUM];
    for (int i = 0; i < THREAD_NUM; ++i) {


        int errorNumber = pthread_create(
                tidArray + i, nullptr,
                [](void* args) -> void* {

                    /*
                     * 每个线程不是计算一次就结束了,
                     * 而是 不停的抢到任务计算, 抢到任务计算。
                     * 所以整个过程是放在一个 while循环中的。
                     */
                    while (true) {
                        // 而各个线程对 number 的访问
                        // 一定是先加锁。
                        /*
                         *todo 笔记: 这里加锁, 对应了下面三种情况的解锁。
                         */
                        pthread_mutex_lock( &MUTEX );
                        // 发现 number为0, 那就出让CPU调度器,
                        // 让主线程去下发任务。
                        while (number == 0) {
                            /*
                             *todo 第 1 种情况。
                             */
                            pthread_mutex_unlock( &MUTEX );
//                            sleep( 1 );
                            sched_yield();
                            /* 在解锁后等待别的线程加锁这段时间,
                             * 可能还是自己抢到到锁,
                             * 所以, 需要让自己这个线程先停一下,
                             * 可以用 sleep(1);
                             * 但是这个不推荐的, sleep(1);
                             * 尽量不要出现在源码中, 除非还在测试。
                             * 而且 sleep(1); 会影响当前线程状态的
                             * 调度颠簸, 当前线程会由 running 态
                             * 变为 sleep 态, 当 sleep(1);调用完后
                             * 线程再由 sleep 态变为 running 态。
                             * 其实这个调度颠簸是没有必要发生的。
                             * 所以, 不要使用 sleep(1);
                             * 而是使用 sched_yield(); (schedule yield: 调度让出)
                             * 函数。 sched_ 开头的函数全部都是针对CPU调度器操作的。
                             * sched_yield();的作用是出让CPU调度器给别的线程使用。
                             * 可以理解为 sched_yield(); 函数是一个非常非常短的
                             * sleep();而且不会造成当前线程的调度颠簸。
                             */
                            pthread_mutex_lock( &MUTEX );
                        }
                        if (number == -1) {
                            // todo 笔记: 注意: 这句跳出临界区
                            //  之前的解锁非常重要！
                            /*
                             *todo 第 2 种情况。
                             */
                            pthread_mutex_unlock( &MUTEX );
                            // 跳出循环之前一定要记得解锁。
                            // 不然就是死锁。
                            /*
                             *todo 笔记:
                             * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                             * 一定一定一定要非常注意临界区内所有的跳转语句！！！！
                             * 例如: break; continue; 函数调用; goto; longjump;
                             * 等等, 如果跳转后的位置仍然在临界区内, 那你就可以忽略掉
                             * 解锁, 因为你还在临界区内。只要跳转到临界区外,
                             * 一定要解锁再跳转。否则就会造成死锁。
                             * 而且临界区代码段尽量不要太长。
                             */
                            break;
                        }

                        int num = number;
                        number = 0;
                        /*
                         *todo 第 3 种情况。
                         */
                        pthread_mutex_unlock( &MUTEX );


                        int mark{1};
                        for (int j = 2; j < num / 2; ++j) {
                            if (num % j == 0) {
                                mark = 0;
                                break;
                            }
                        }
                        if (mark == 1) {
                            // 注意: 这里使用 pthread_self(); 在 %lu 输出其实是不推荐的。
                            // 但是考虑到仅仅是为了打印出来查看, 也没什么大不了的。
                            printf( "[%lu]: %d 是一个质数。\n", pthread_self(), num );
                        }

                    }


                    pthread_exit( args );

                },
                nullptr );
        if (errorNumber != 0) {
            fprintf( stderr, "pthread_create(): %s.\n", strerror(errno));
            return 1;
        }

    }

    // main线程, 下发任务环节。
    for (int i = LEFT; i <= RIGHT; ++i) {

        // 为了在多线程中对 number 的访问不造成竞争。
        // 所以需要对 number 的访问加锁保护。
        pthread_mutex_lock( &MUTEX );
        // 加锁后, 也不能直接将 任务i 的值 赋值给 number,
        // 而是要对 number 的值进行判断。
        // 而且不能使用 if判断, 因为 if判断 不是就过去了。
        // 而是使用 while判断。
        /*
         * 这里是有盲等的, 所以CPU的使用率一下子就上去了。
         * 而且程序运行起来是一卡一卡的。
         *todo 笔记: 盲等在所有的线程都在不停的查看自己的状态(轮询自己的状态)！！！！
         * 确保状态是自己要的那个状态。
         * 所有后续的改进可以将轮询法改为通知法, 用一个东西通知各个线程
         * 这个 number值 的状态。这就是条件变量！！！！
         *
         *
         * 书签标记的地方都是有盲等的代码。
         */
        /*if*/while (number != 0) {
            /* 如果这个任务还没有线程拿到, 那就
             * 先对 number 的访问解锁, 好让其它线程来访问 number值,
             * 然后置为 0。
             */
            pthread_mutex_unlock( &MUTEX );
//            sleep( 1 );
            sched_yield();
            // 解锁后, 再等其它线程拿到锁后, 这里再加锁。
            pthread_mutex_lock( &MUTEX );
        }

        number = i;
        pthread_mutex_unlock( &MUTEX );

    }

    // 置为 -1, 是作为任务下发终止状态, 让各个线程正常退出的。
    // 因为也访问了 number值, 所以也需要加锁保护。
    /* 这段代码其实可以合并到上面的循环的最后。
     */
    pthread_mutex_lock( &MUTEX );
    while (number != 0) {
        pthread_mutex_unlock( &MUTEX );
        sched_yield();
        pthread_mutex_lock( &MUTEX );
    }
    number = -1;
    pthread_mutex_unlock( &MUTEX );


    for (int i = 0; i < THREAD_NUM; ++i) {
        pthread_join( tidArray[i], nullptr );
    }


    pthread_mutex_destroy( &MUTEX );


    return 0;
}
